﻿#region Using directives

using System;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;


using Weazel.Gezel.Model;
using GezelModel = Weazel.Gezel.Model.Model;

using Weazel.Gezel.Conversions.Model;
using Weazel.Gezel.Conversions.CodeGeneration;
using SoftwareType = Weazel.Gezel.Conversions.Model.Type;


#endregion

namespace Weazel.Gezel.Conversions.Model
{
	public class SoftwareModel
	{
		private readonly SimulatorType simulatorType;

		/// <summary>
		/// Returns the software type which drives the
		/// main simulation loop.
		/// </summary>
		/// <value></value>
		public SimulatorType SimulatorType
		{
			get { return simulatorType; }
		}

		private PortMap portMappings;
		public PortMap PortMappings
		{
			get { return portMappings; }
		}

		private SignalMap signalMappings;
		public SignalMap SignalMappings
		{
			get { return signalMappings; }
		}

		private List<TracerType> tracerTypes;
		public List<TracerType> TracerTypes
		{
			get { return tracerTypes; }
		}

		private readonly Dictionary<string, ControllerType> controllerTypes;
		public Dictionary<string, ControllerType> ControllerTypes
		{
			get { return controllerTypes; }
		}

		private readonly Dictionary<string, OutPortType> outportTypes;
		public Dictionary<string, OutPortType> OutportTypes
		{
			get { return outportTypes; }
		}

		private readonly Dictionary<string, RegisterType> registerTypes;
		public Dictionary<string, RegisterType> RegisterTypes
		{
			get { return registerTypes; }
		}

		private readonly Dictionary<string, SignalType> signalTypes;
		public Dictionary<string, SignalType> SignalTypes
		{
			get { return signalTypes; }
		}

		private readonly Dictionary<string, InPortType> inportTypes;
		public Dictionary<string, InPortType> InportTypes
		{
			get { return inportTypes; }
		}

		private readonly Dictionary<string, DisplayType> displayTypes;
		public Dictionary<string, DisplayType> DisplayTypes
		{
			get
			{
				return displayTypes;
			}
		}

		private TargetLanguage language;
		public TargetLanguage Language
		{
			get { return language; }
		}

		/// <summary>
		/// Creates a new <see cref="SoftwareModel"/> instance from
		/// the corresponding gezel hardware model.
		/// </summary>
		/// <param name="gezelModel">Gezel HDL model</param>
		public SoftwareModel(GezelModel gezelModel, TargetLanguage language)
		{
			this.language = language;
			this.outportTypes = new Dictionary<string, OutPortType>();
			this.controllerTypes = new Dictionary<string, ControllerType>();
			this.registerTypes = new Dictionary<string, RegisterType>();
			this.signalTypes = new Dictionary<string, SignalType>();
			this.portMappings = new PortMap();
			this.signalMappings = new SignalMap();
			this.tracerTypes = new List<TracerType>();
			this.displayTypes = new Dictionary<string, DisplayType>();
			this.inportTypes = new Dictionary<string, InPortType>();

			buildPortMappings(gezelModel);

			createTypes(gezelModel);

			analyzeDependencies();

			this.simulatorType = new SimulatorType(this);

			postAnalyzeDependencies();
		}

		public SoftwareModel(GezelModel gezelModel)
			: this(gezelModel, TargetLanguage.CSharp)
		{
		}

		private void analyzeDependencies()
		{
			Console.Error.WriteLine("Analyzing dependencies...");

			foreach (ControllerType controllerType in controllerTypes.Values)
			{
				controllerType.AnalyzeDependencies();
			}

			foreach (RegisterType registerType in registerTypes.Values)
			{
				registerType.AnalyzeDependencies();
			}

			foreach (OutPortType outportType in outportTypes.Values)
			{
				outportType.AnalyzeDependencies();
			}

			foreach (SignalType signalType in signalTypes.Values)
			{
				signalType.AnalyzeDependencies();
			}
		}

		private void postAnalyzeDependencies()
		{
			Console.Error.WriteLine("Generating {0} code for {1} types...", language, getTypeCount());

			foreach (ControllerType controllerType in controllerTypes.Values)
			{
				controllerType.PostAnalyzeDependencies();
			}

			//      foreach (RegisterType registerType in registerTypes.Values)
			//      {
			//        registerType.PostAnalyzeDependencies();
			//      }
			//
			foreach (OutPortType outportType in outportTypes.Values)
			{
				outportType.PostAnalyzeDependencies();
			}

      foreach (SignalType signalType in signalTypes.Values)
      {
        signalType.PostAnalyzeDependencies();
      }
		}

		private void buildPortMappings(GezelModel gezelModel)
		{
			Console.Error.WriteLine("Building port map...");
			foreach (system system in gezelModel.Systems.Values)
			{
				foreach (system.Net net in system.Nets.Values)
				{
					foreach (InPort inPort in net.InPorts)
					{
						System.Diagnostics.Debug.Assert(!portMappings.ContainsInport(inPort));
						portMappings[inPort] = net.OutPort;
					}
				}
			}

			foreach (Datapath datapath in gezelModel.Datapaths.Values)
			{
				foreach (string childdpName in datapath.ChildDatapathRefs.Keys)
				{
					string[] connections = datapath.ChildDatapathRefs[childdpName];
					for (int i = 0; i < connections.Length; i++)
					{
						string connection = connections[i];
						Block childDp;
						if (gezelModel.Datapaths.ContainsKey(childdpName))
						{
							childDp = gezelModel.Datapaths[childdpName];
						}
						else
						{
							System.Diagnostics.Debug.Assert(gezelModel.IPBlocks.ContainsKey(childdpName));
							childDp = gezelModel.IPBlocks[childdpName];
						}

						Port childPort = childDp.GetPortAtPosition(i);

						// add port to map
						if (childPort is InPort && datapath.InPorts.ContainsKey(connection))
						{
							OutPort outport = portMappings[datapath.InPorts[connection]];
							InPort inport = childPort as InPort;
							//Console.WriteLine("added {0} to portmap", inport);
							portMappings[inport] = outport;
						}
						else if (childPort is InPort && datapath.Signals.ContainsKey(connection))
						{
							Signal sig = datapath.Signals[connection];
							InPort inport = childPort as InPort;
							if (sig.OutportDriver != null)
							{
								//Console.WriteLine("added {0} to portmap", inport);
								portMappings[inport] = sig.OutportDriver;
							}
							else
							{
								signalMappings[inport] = sig;	
							}
						}
						else if (childPort is InPort)
						{
							Console.WriteLine("Warning, could not add {0} to portmap ({1})", childPort, childPort.GetType().Name);
						}
						else
						{
							System.Diagnostics.Trace.Assert(childPort is OutPort, "fåk");
						}
					}
				}
			}
		}

		private void createTypes(GezelModel gezelModel)
		{
			Console.Error.WriteLine("Creating software types...");
			foreach (Controller controller in gezelModel.Controllers.Values)
			{
				// skip datapaths which are not part of the simulation
				if (!controller.Datapath.InSystem)
					continue;

				ControllerType controllerType = controllerTypes[controller.Name] = new ControllerType(controller, this);

				Datapath datapath = controller.Datapath;

				foreach (Register register in datapath.Registers.Values)
				{
					RegisterType registerType = new RegisterType(register, controllerType, this);
					registerTypes[register.Name] = registerType;
					controllerType.RegisterTypes[register.Name] = registerType;
				}

				foreach (OutPort outport in datapath.OutPorts.Values)
				{
					OutPortType outportType = new OutPortType(outport, controllerType, this);
					outportTypes[outport.Name] = outportType;
					controllerType.OutportTypes[outport.Name] = outportType;
				}

				foreach (Signal signal in datapath.Signals.Values)
				{
					SignalType signalType = new SignalType(signal, controllerType, this);
					signalTypes[signal.Name] = signalType;
					controllerType.SignalTypes[signal.Name] = signalType;
				}

				foreach (SignalTrace trace in datapath.Tracers)
				{
					TracerType tracerType = new TracerType(trace, this);
					tracerTypes.Add(tracerType);
				}

				foreach (SignalFlowGraph sfg in datapath.SignalFlowGraphs.Values)
				{
					foreach (Expression expr in sfg.Expressions)
					{
						if (expr.ExpressionType == ExpressionType.DisplayDirective)
						{
							DisplayType display = new DisplayType(expr as DirectiveExpression, sfg, controllerType, this);
							displayTypes[display.Name] = display;
						}
					}
				}
			}

			foreach (IPBlock ipblock in gezelModel.IPBlocks.Values)
			{
				//System.Diagnostics.Debug.Assert(ipblock.IpType == "queue");
				foreach (InPort inPort in ipblock.InPorts.Values)
				{
					InPortType inportType = new InPortType(inPort, this);
					InportTypes[inPort.Name] = inportType;
				}
				foreach (OutPort outPort in ipblock.OutPorts.Values)
				{
					OutPortType outportType = new OutPortType(outPort, null, this);
					OutportTypes[outPort.Name] = outportType;
				}
			}
		}

		/// <summary>
		/// Helper method which counts the number of types
		/// </summary>
		/// <returns></returns>
		private int getTypeCount()
		{
			return controllerTypes.Count +
							outportTypes.Count +
							inportTypes.Count +
							registerTypes.Count +
							signalTypes.Count +
							tracerTypes.Count +
							displayTypes.Count +
							1; // for the simulator type
		}

		public SoftwareType[] GetAllTypes()
		{
			List<SoftwareType> types = new List<SoftwareType>();

			foreach (ControllerType controllerType in controllerTypes.Values)
				types.Add(controllerType);

			foreach (OutPortType outportType in outportTypes.Values)
				types.Add(outportType);

			foreach (InPortType inportType in inportTypes.Values)
				types.Add(inportType);

			foreach (RegisterType registerType in registerTypes.Values)
				types.Add(registerType);

			foreach (SignalType signalType in signalTypes.Values)
				types.Add(signalType);

			foreach (DisplayType displayType in displayTypes.Values)
				types.Add(displayType);

			types.AddRange(tracerTypes.ToArray());

			types.Add(simulatorType);

			return types.ToArray();
		}

		public RegisterType GetTypeForRegister(Register register)
		{
			return registerTypes[register.Name];
		}

		public OutPortType GetTypeForOutport(OutPort outport)
		{
			return outportTypes[outport.Name];
		}

		public SignalType GetTypeForSignal(Signal signal)
		{
			return signalTypes[signal.Name];
		}
	}
}
